package com.suray.system.run.random.code.thread;

import com.suray.basic.wcs.path.core.Node;
import com.suray.basic.wcs.path.enums.DirectionStatus;
import com.suray.basic.wcs.path.enums.NodeType;
import com.suray.basic.wcs.path.properties.PathProperties;
import com.suray.basic.wcs.utils.*;
import com.suray.basic.wcs.utils.thread.BaseThread;
import com.suray.commin.core.utils.SpringContextHolder;
import com.suray.commin.core.utils.StringUtils;
import com.suray.wcs.res.enums.RgvStatus;
import com.suray.wcs.service.enums.RunForGCodeStatus;
import com.suray.wcs.service.enums.TaskRgvProgress;
import com.suray.wcs.service.enums.TaskStatus;
import com.suray.wcs.service.enums.TaskType;
import com.suray.wcs.service.pojo.Rgv;
import com.suray.wcs.service.pojo.rgv.action.util.RgvTaskMode;
import com.suray.wcs.service.rescontact.ToRes;
import com.suray.wcs.service.system.entity.NodeDB;
import com.suray.wcs.service.system.entity.TaskDB;
import com.suray.wcs.service.system.entity.WareHouseDB;
import com.suray.wcs.service.system.service.impl.NodeDBServiceImpl;
import com.suray.wcs.service.system.service.impl.RgvDBServiceImpl;
import com.suray.wcs.service.system.service.impl.TaskDBServiceImpl;
import com.suray.wcs.service.system.service.impl.WareHouseDBServiceImpl;
import com.suray.wcs.service.thread.RgvExecuteThread;
import com.suray.wcs.service.util.NodeDirection;
import com.suray.wcs.service.util.WcsUtil;

import java.util.ArrayList;
import java.util.List;

public class RunForGCodeThread extends BaseThread {
    private final TaskDBServiceImpl taskService = SpringContextHolder.getBean(TaskDBServiceImpl.class);
    private final static NodeDBServiceImpl nodeDBService = SpringContextHolder.getBean(NodeDBServiceImpl.class);
    private RgvDBServiceImpl rgvDBService = SpringContextHolder.getBean(RgvDBServiceImpl.class);
    private WareHouseDBServiceImpl wareHouseDBService = SpringContextHolder.getBean(WareHouseDBServiceImpl.class);

    private Rgv rgv;

    public RunForGCodeThread(Rgv rgv) {
        super(rgv.getRgvName() + "跑码", 1000, ModuleType.RUN_FOR_GCODE);
        this.rgv = rgv;
    }

    @Override
    protected void process() {
        WareHouseDB wareHouseDB = wareHouseDBService.getOne(null);
        if(wareHouseDB.getRunFlag().equals("1")) {
            doTask();
        }
    }

    private void doTask() {
        TaskDB taskDB = taskService.findRunForGCodeTask(rgv.getRgvDB().getRgvNo());

        if (taskDB != null
                && taskDB.getTaskStatus() != TaskStatus.ERROR
                && rgv.getCurLocation() != null
                && (RgvStatus.READY.equals(rgv.getRgvStatus()) || RgvStatus.CHARGING.equals(rgv.getRgvStatus()))
                && RgvExecuteThread.isLowElectricAndWmsTaskOrSufTask(rgv, taskDB, taskService)
                && RgvExecuteThread.isCanExecuteTaskBattery(rgv)) {
            try {
                List<Node> pathNodes = analyzePathNodes(taskDB.getPath());

                rgv.setRgvTaskMode(RgvTaskMode.RUN_FOR_GROUND_CODE);
                ToRes.getInstance(rgv).swicthCalibitionMode(null, false);
                updateRgvCodeCalibration(false);
                runPath(taskDB, pathNodes, rgv, taskService);
                ToRes.getInstance(rgv).swicthCalibitionMode(null, true);
                updateRgvCodeCalibration(true);
            } catch (Exception e) {
                WcsFuncUtil.exceptionLog(e, this.getClass());
                WcsUtil.notification(rgv.getRgvName() + "跑码出错:" + e.getMessage());
                rgv.setRgvStatus(RgvStatus.ERROR);
                if (StringUtils.isEmpty(rgv.getErrorMessage())) {
                    rgv.setErrorMessage("跑码出错:" + e.getMessage());
                }
                taskService.updateTaskStatus(taskDB, TaskStatus.ERROR);
            } finally {
                rgv.setRgvTaskMode(RgvTaskMode.NORMAL);
            }
        }
    }

    private void updateRgvCodeCalibration(boolean calibration) {
        rgv.getRgvDB().setCodeCalibration(calibration);
        rgvDBService.updateById(rgv.getRgvDB());

    }

    private void runPath(TaskDB taskDB, List<Node> pathNodes, Rgv rgv, TaskDBServiceImpl taskService) {
        if (pathNodes.size() >= 2) {
            LoggerUtil.info(ModuleType.RUN_FOR_GCODE, LoggerType.INFO, taskDB.getTaskId(), "开始执行扫码任务",
                    "路径：" + WcsFuncUtil.listToString(pathNodes), this.getClass());

            TaskDB task = null;
            if (!(taskDB.getConnectTaskId() == null || taskDB.getConnectTaskId() == 0)) {
                task = taskService.getById(taskDB.getConnectTaskId());
            }

            if (taskDB.getRgvProgress() == TaskRgvProgress.RGV_TO_START && task != null) {
                long time = System.currentTimeMillis();
                while (task.getTaskStatus() != TaskStatus.FINISH) {
                    checkInterrupt("等待任务结束:" + task.getTaskId(), time);
                    WcsFuncUtil.delay(1000);
                    task = taskService.getById(task);
                }
                taskService.updateConnnectTaskId(null, taskDB.getTaskId(), 0);
            }

            if (taskDB.getRgvProgress() != TaskRgvProgress.RGV_TO_END
                    && !rgv.getCurLocation().equals(taskDB.selectStartNode())) {
                taskService.updateTaskRgvProgress(taskDB, TaskRgvProgress.RGV_TO_START);
                rgvRunToStart(taskDB.selectStartNode(), taskDB);
            }
            taskService.updateTaskRgvProgress(taskDB, TaskRgvProgress.RGV_TO_END);

            try {
                if (task == null || !rgv.getCurLocation().equals(task.selectEndNode())) {
                    ToRes.getInstance(rgv).calibrationNode();
                    updateNodeGroundCode(rgv.getCurLocation());
                }
            } catch (Exception e) {
                updateLineNodeStatus(rgv, rgv.getCurLocation(), RunForGCodeStatus.FAIL);
                throw e;
            }

            Node curNode = taskDB.selectStartNode();
            for (int i = 0; i < (pathNodes.size() - 1);) {
                Node start = pathNodes.get(i);
                Node end = pathNodes.get(++i);
                if (curNode != null) {
                    if (runPath(start, end, rgv, curNode, taskService, taskDB)) {
                        curNode = null;
                    }
                } else {
                    runPath(start, end, rgv, start, taskService, taskDB);
                }
            }
        }

        taskService.updateTaskRgvProgress(taskDB, TaskRgvProgress.RGV_COMPLETE);
        taskService.updateTaskStatus(taskDB, TaskStatus.FINISH);
    }

    private void updateLineNodeStatus(Rgv rgv, Node node, RunForGCodeStatus status) {
        if (rgv.getDirectionStatus() == DirectionStatus.XD_1 || rgv.getDirectionStatus() == DirectionStatus.XD_13) {
            if (PathProperties.RAMP_OR_WAY == DirectionStatus.XD_13) {
                updateRunForGCodeStatus(node, status, null, NodeDirection.LEFT);
                updateRunForGCodeStatus(node, status, null, NodeDirection.RIGHT);
            } else if (PathProperties.RAMP_OR_WAY == DirectionStatus.PD_24) {
                updateRunForGCodeStatus(node, status, null, NodeDirection.UP);
                updateRunForGCodeStatus(node, status, null, NodeDirection.DOWN);
            } else {
                throw new RuntimeException("WcsProperties中的RAMP_OR_WAY参数配置错误");
            }
        } else if (rgv.getDirectionStatus() == DirectionStatus.PD_2
                || rgv.getDirectionStatus() == DirectionStatus.PD_24) {
            if (PathProperties.RAMP_OR_WAY == DirectionStatus.XD_13) {
                updateRunForGCodeStatus(node, status, null, NodeDirection.UP);
                updateRunForGCodeStatus(node, status, null, NodeDirection.DOWN);
            } else if (PathProperties.RAMP_OR_WAY == DirectionStatus.PD_24) {
                updateRunForGCodeStatus(node, status, null, NodeDirection.LEFT);
                updateRunForGCodeStatus(node, status, null, NodeDirection.RIGHT);
            } else {
                throw new RuntimeException("WcsProperties中的RAMP_OR_WAY参数配置错误");
            }
        } else {
            throw new RuntimeException(rgv.getRgvName() + "换向状态错误:" + rgv.getDirectionStatus());
        }
    }

    /**
     * 小车前往跑码任务起点
     * @param startNode
     * @param parentTask
     */
    private void rgvRunToStart(Node startNode, TaskDB parentTask) {
        TaskDB taskDB = WcsUtil.generalTask(null, startNode, rgv, TaskType.MOVE_UNLOADED, taskService,
                this.getClass());
        taskService.updateConnnectTaskId(null, parentTask.getTaskId(), taskDB.getTaskId());

        while (taskDB.getTaskStatus() != TaskStatus.FINISH) {
            if (rgv.getCurLocation().equals(startNode) && rgv.getRgvStatus() == RgvStatus.READY) {
                break;
            }

            WcsFuncUtil.delay(1000);
        }

        taskService.updateConnnectTaskId(null, parentTask.getTaskId(), 0);
        LoggerUtil.info(rgv.getRgvName() + "到达跑码起点:" + startNode + ", 状态:" + rgv.getRgvStatus(),
                this.getClass());
    }

    /**
     *
     * @param start
     * @param end
     * @param rgv
     * @param curRgvNode : 小车当前跑码任务的起点
     * @param taskService
     * @param parentTask
     */
    private boolean runPath(Node start, Node end, Rgv rgv, Node curRgvNode, TaskDBServiceImpl taskService,
                         TaskDB parentTask) {
        boolean match = false;
        if (start.equals(end)) {
            return match;
        }

        if (start.getZ() != end.getZ()) {
            throw new RuntimeException("路径坐标不在同一层:" + start + "-" + end);
        }

        if (start.getX() == end.getX()) {
            int offset = (end.getY() - start.getY()) / Math.abs(end.getY() - start.getY());
            for (int i = start.getY(); i != end.getY();) {
                Node taskStart = new Node(start.getX(), i, start.getZ());
                i += offset;
                if (!taskStart.equals(curRgvNode)) {
                    //不属于跑码任务起点，忽略点位（用于任务恢复）
                    continue;
                }
                match = true;

                Node taskEnd = new Node(start.getX(), i, start.getZ());

                rgvExecTask(rgv, taskStart, taskEnd, parentTask, taskService);

                curRgvNode = taskEnd;
            }
        } else if (start.getY() == end.getY()) {
            int offset = (end.getX() - start.getX()) / Math.abs(end.getX() - start.getX());
            for (int i = start.getX(); i != end.getX();) {
                Node taskStart = new Node(i, start.getY(), start.getZ());
                i += offset;
                if (!taskStart.equals(curRgvNode)) {
                    //不属于跑码任务起点，忽略点位（用于任务恢复）
                    continue;
                }
                match = true;

                Node taskEnd = new Node(i, start.getY(), start.getZ());

                rgvExecTask(rgv, taskStart, taskEnd, parentTask, taskService);

                curRgvNode = taskEnd;
            }
        } else {
            throw new RuntimeException("路径坐标不在一条直线上:" + start + "-" + end);
        }

        return match;
    }

    /**
     * 任务执行
     * @param rgv
     * @param taskStart
     * @param taskEnd
     * @param parentTask
     * @param taskService
     */
    private void rgvExecTask(Rgv rgv, Node taskStart, Node taskEnd, TaskDB parentTask, TaskDBServiceImpl taskService) {
        TaskDB taskDB = null;
        parentTask = taskService.getById(parentTask);

        boolean isTaskEnd = false;
        if (parentTask.getConnectTaskId() == null || parentTask.getConnectTaskId() == 0) {
            taskDB = WcsUtil.generalTask(taskStart, taskEnd, rgv, TaskType.MOVE_UNLOADED,
                    taskService, 1000, this.getClass());
            taskService.updateConnnectTaskId(null, parentTask.getTaskId(), taskDB.getTaskId());
            LoggerUtil.info(ModuleType.RUN_FOR_GCODE, LoggerType.INFO, taskDB.getTaskId(), "生成行驶任务",
                    taskDB.toString(), this.getClass());
        } else {
            taskDB = taskService.getById(parentTask.getConnectTaskId());

            if (taskDB.getTaskStatus() == TaskStatus.FINISH) {
                isTaskEnd = true;
            }
        }

        try {
            if (!isTaskEnd) {
                long time = System.currentTimeMillis();
                while (taskDB.getTaskStatus() != TaskStatus.FINISH) {
                    checkInterrupt("等待任务结束:" + taskDB.getTaskId(), time);
                    if (taskDB.getTaskStatus() == TaskStatus.CANCEL) {
                        throw new RuntimeException("任务" + taskDB.getTaskId() + "被取消");
                    }
                    WcsFuncUtil.delay(1000);
                    taskDB = taskService.getById(taskDB);
                }
            }

            if (!rgv.getCurLocation().equals(taskDB.selectEndNode()) ) {
                throw new RuntimeException("任务" + taskDB.getTaskId() + "已经完成，但是小车不在任务终点，请把小车开至" +
                        taskDB.selectEndNode() + "再恢复任务：" + parentTask.getTaskId());
            }

            int calibrationTime = ToRes.getInstance(rgv).getCalibrationTime(null);
            ToRes.getInstance(rgv).calibrationNode();
            updateNodeGroundCode(taskEnd);

            taskService.updateConnnectTaskId(taskEnd, parentTask.getTaskId(), 0);
            updateRunForGCodeStatus(taskEnd, RunForGCodeStatus.PASS, calibrationTime,
                    analyzeDirection(taskDB.selectStartNode(), taskDB.selectEndNode()));
            LoggerUtil.info(ModuleType.RUN_FOR_GCODE, LoggerType.INFO, taskDB.getTaskId(),
                    "--行驶任务完成,"+taskDB.getEndNodeStr()+"更新校准时间为:" + calibrationTime + "ms",
                    taskDB.toString(), this.getClass());
        } catch (Exception e) {
            updateRunForGCodeStatus(taskEnd, RunForGCodeStatus.FAIL, null,
                    analyzeDirection(taskDB.selectStartNode(), taskDB.selectEndNode()));
            throw e;
        }
    }

    /**
     * 更新点位地码
     * @param taskEnd
     */
    private void updateNodeGroundCode(Node taskEnd) {
        LoggerUtil.info(taskEnd + "更新点位条码:" + rgv.getGroundCode(), this.getClass());
        NodeDB nodeDB = nodeDBService.getNodeDB(taskEnd);
        String groundCode = rgv.getGroundCode();
        if (StringUtils.isEmpty(groundCode)) {
            throw new RuntimeException("未扫到条码, 换向状态：" + rgv.getDirectionStatus());
        }

        if (!rgv.getCurLocation().equals(taskEnd)) {
            throw new RuntimeException("小车" + rgv.getCurLocation() + "当前位置与任务终点不一致:" + taskEnd);
        }

        ToRes toRes = ToRes.getInstance(rgv);
        if (nodeDB.getGridType() == NodeType.R) {
            //坡道条码更新
            if (rgv.getDirectionStatus() == DirectionStatus.XD_13
                    || rgv.getDirectionStatus() == DirectionStatus.XD_1) {
                toRes.changeToRamp(null);
            } else if (rgv.getDirectionStatus() == DirectionStatus.PD_24
                    || rgv.getDirectionStatus() == DirectionStatus.PD_2) {
                toRes.changeToRoadway(null);
            } else {
                throw new RuntimeException("小车当前换向状态错误：" + rgv.getDirectionStatus());
            }

            String groundCode2 = rgv.getGroundCode();
            if (StringUtils.isEmpty(groundCode2)) {
                throw new RuntimeException("未扫到条码, 换向状态：" + rgv.getDirectionStatus());
            }

            if (!groundCode.equals(groundCode2)) {
                throw new RuntimeException("换向前与换向后扫到的条码不一致：" + groundCode + ", " + groundCode2);
            }

            toRes.calibrationNode();
        }

        List<NodeDB> nodeByGroundCodes = nodeDBService.getNodeDBsByGroundCode(rgv.getGroundCode());
        if (nodeByGroundCodes != null && nodeByGroundCodes.size() > 0) {
            for (NodeDB nodeByGroundCode : nodeByGroundCodes) {
                if (nodeByGroundCode.getGridx() != nodeDB.getGridx()
                        || nodeByGroundCode.getGridy() != nodeDB.getGridy()
                        || nodeByGroundCode.getGridz() != nodeDB.getGridz()) {
                    throw new RuntimeException("地码已经存在，请更换当前位置的地码，重复位置:(" + nodeByGroundCode.getGridx()
                            + "," + nodeByGroundCode.getGridy() + "," + nodeByGroundCode.getGridz() + ")");
                }
            }
        }

        NodeDB n = new NodeDB();
        n.setNodeId(nodeDB.getNodeId());
        n.setGroundCode(rgv.getGroundCode());
        nodeDBService.updateById(n);
    }

    private void checkInterrupt(String msg, long time) {
        rgv.checkInterrupt(msg, time);

        if (rgv.getRgvStatus() == RgvStatus.ERROR) {
            throw new RuntimeException("小车故障");
        }
    }

    /**
     * 解析点位方向
     * @param startCoord
     * @param endCoord
     * @return
     */
    private NodeDirection analyzeDirection(Coord startCoord, Coord endCoord) {
        if (startCoord.getZ() != endCoord.getZ()) {
            throw new RuntimeException("坐标不在同一层:" + startCoord + endCoord);
        }

        if (startCoord.getX() == endCoord.getX()) {
            if (endCoord.getY() > startCoord.getY()) {
                return NodeDirection.LEFT;
            } else if (endCoord.getY() < startCoord.getY()) {
                return NodeDirection.RIGHT;
            } else {
                throw new RuntimeException("坐标重合:" + startCoord + endCoord);
            }
        } else if (startCoord.getY() == endCoord.getY()) {
            if (endCoord.getX() > startCoord.getX()) {
                return NodeDirection.UP;
            } else if (endCoord.getX() < startCoord.getX()) {
                return NodeDirection.DOWN;
            } else {
                throw new RuntimeException("坐标重合:" + startCoord + endCoord);
            }
        } else {
            throw new RuntimeException("坐标不在同一直线:" + startCoord + endCoord);
        }
    }

    /**
     * 更新点位跑码和校准时间信息
     * @param node
     * @param status
     * @param calibrationTime
     * @param direction
     */
    private void updateRunForGCodeStatus(Node node, RunForGCodeStatus status, Integer calibrationTime,
                                         NodeDirection direction) {
        nodeDBService.updateRunForGCodeStatus(node, status.getValue(), calibrationTime, direction);
        switch (direction) {
            case UP:
                Node n1 = new Node(node.getX() - 1, node.getY(), node.getZ());
                nodeDBService.updateRunForGCodeStatus(n1, status.getValue(), calibrationTime, NodeDirection.DOWN);
                break;
            case DOWN:
                Node n2 = new Node(node.getX() + 1, node.getY(), node.getZ());
                nodeDBService.updateRunForGCodeStatus(n2, status.getValue(), calibrationTime, NodeDirection.UP);
                break;
            case LEFT:
                Node n3 = new Node(node.getX(), node.getY() - 1, node.getZ());
                nodeDBService.updateRunForGCodeStatus(n3, status.getValue(), calibrationTime, NodeDirection.RIGHT);
                break;
            case RIGHT:
                Node n4 = new Node(node.getX(), node.getY() + 1, node.getZ());
                nodeDBService.updateRunForGCodeStatus(n4, status.getValue(), calibrationTime, NodeDirection.LEFT);
                break;
            default:
                throw new RuntimeException(node + "错误的NodeDirection:" + direction);
        }
        LoggerUtil.info(node + "更新点位跑码结果：" + status + "-" + calibrationTime, this.getClass());
    }

    /**
     * 解析路径
     * @param path
     * @return
     */
    public static List<Node> analyzePathNodes(String path) {
        List<Node> pathNodes = new ArrayList<>();

        if (StringUtils.isEmpty(path)) {
            return pathNodes;
        }

        for (String s : path.split("_")) {
            pathNodes.add(Node.parseGrid(s));
        }

        return pathNodes;
    }
}
